/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.core;
import java.io.*;
import java.text.MessageFormat;
import java.util.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.openide.loaders.DataLoader;
import org.openide.loaders.DataLoaderPool;
import org.openide.loaders.InstanceSupport;
import org.openide.modules.ManifestSection;
import org.openide.TopManager;
import org.openide.actions.*;
import org.openide.nodes.*;
import org.openide.util.actions.SystemAction;
import org.openide.util.enum.ArrayEnumeration;
import org.openide.util.*;
import org.openide.util.io.NbMarshalledObject;
import org.openide.actions.ReorderAction;
/** Node which represents loader pool and its content - all loaders
* in the system. LoaderPoolNode also supports subnode reordering.<P>
* LoaderPoolNode is singleton and that's why it can be obtained
* only via call to static factory method getLoaderPoolNode().<P>
* The same situation applies for NbLoaderPool inner class.
* Instance of CoronaLoaderPool (the only instance in the system) you
* can obtain through getNbLoaderPool().
* @author Dafe Simonek
*/
public final class LoaderPoolNode extends AbstractNode {
/** Default icon base for loader pool node.*/
private static final String LOADER_POOL_ICON_BASE =
"/org/netbeans/core/resources/loaderPool"; // NOI18N
/** The only instance of the LoaderPoolNode class in the system.
* This value is returned from the getLoaderPoolNode() static method */
private static LoaderPoolNode loaderPoolNode;
/** The only instance of the NbLoaderPool class in the system.
* This value is returned from the getNbLoaderPool() static method */
private static LoaderPoolNode.NbLoaderPool loaderPool;
private static LoaderChildren myChildren = new LoaderChildren ();
/** Array of DataLoader objects */
private static List loaders = new ArrayList ();
/** Map from loader class names to arrays of class names for Install-Before's */
private static Map installBefores = new HashMap ();
/** Map from loader class names to arrays of class names for Install-After's */
private static Map installAfters = new HashMap ();
/** copy of the loaders to prevent copying */
private static Object[] loadersArray;
/** true if changes in loaders should be notified */
private static boolean installationFinished = false;
/** Just workaround, need to pass instance of
* the LoaderPoolNodeChildren as two params to superclass
*/
private LoaderPoolNode () {
super (myChildren);
setName(NbBundle.getBundle(LoaderPoolNode.class).
getString("CTL_LoaderPool"));
setIconBase(LOADER_POOL_ICON_BASE);
getCookieSet ().add (new Index ());
getCookieSet ().add (new InstanceSupport.Instance (getNbLoaderPool ()));
}
public HelpCtx getHelpCtx () {
return new HelpCtx (LoaderPoolNode.class);
}
/** Getter for set of actions that should be present in the
* popup menu of this node.
*
* @return array of system actions that should be in popup menu
*/
public SystemAction[] createActions () {
return new SystemAction[] {
SystemAction.get(CustomizeBeanAction.class),
null,
SystemAction.get(ReorderAction.class),
null,
SystemAction.get(ToolsAction.class),
SystemAction.get(PropertiesAction.class),
};
}
/** Adds new loader at the end of existing ones.
* @param dl data loader to add
* @exception IllegalArgumentException if the loader is already there
*
private static void add (DataLoader dl) {
myChildren.addLoader(dl);
}
/** Adds new loader at the end of existing ones.
* @param dl data loader to add
* @param at the position to insert it the loader to
* @exception IllegalArgumentException if the loader is already there
*
private static void add (DataLoader dl, int at) {
myChildren.addLoader(dl, at);
}
/** Adds new loader when previous and following are specified.
* An attempt will be made to (re-)order the loader pool according to specified
* dependencies.
* <p>If a loader of the same class already existed in the pool, that will be <b>removed</b>
* and replaced with the new one.
* @param s adds loader section
*/
public static synchronized void add (ManifestSection.LoaderSection s) throws InstantiationException {
DataLoader l = s.getLoader ();
Iterator it = loaders.iterator ();
while (it.hasNext ())
if (it.next ().getClass ().equals (l.getClass ()))
it.remove ();
loaders.add (l);
installBefores.put (l.getClass ().getName (), s.getInstallBefore ());
installAfters.put (l.getClass ().getName (), s.getInstallAfter ());
resort ();
}
/** Resort the loader pool according to stated dependencies.
* Attempts to keep a stable order whenever possible, i.e. more-recently-installed
* loaders will tend to stay near the end unless they need to be moved forward.
* Note that dependencies on nonexistent (or unloadable) representation classes are simply
* ignored and have no effect on ordering.
* If there is a cycle (contradictory set of dependencies) in the loader pool,
* its order is not changed.
* In any case, a change event is fired afterwards.
*/
private static synchronized void resort () {
// A partial ordering over loaders based on their Install-* tags:
Comparator c = new Comparator () {
public int compare (Object o1, Object o2) {
if (o1 == o2) return 0;
String l1 = o1.getClass ().getName ();
String l2 = o2.getClass ().getName ();
String rep1 = ((DataLoader) o1).getRepresentationClass ().getName ();
String rep2 = ((DataLoader) o2).getRepresentationClass ().getName ();
// Determine if either of them specify an Install-After or Install-Before on the other.
boolean mustbe12 = false;
String[] befores1 = (String[]) installBefores.get (l1);
if (befores1 != null) {
for (int i = 0; i < befores1.length; i++) {
if (befores1[i].equals (rep2)) {
mustbe12 = true;
break;
}
if (befores1[i].equals (l2)) warn (l1, l2, rep2);
}
}
if (! mustbe12) {
String[] afters2 = (String[]) installAfters.get (l2);
if (afters2 != null) {
for (int i = 0; i < afters2.length; i++) {
if (afters2[i].equals (rep1)) {
mustbe12 = true;
break;
}
if (afters2[i].equals (l1)) warn (l2, l1, rep1);
}
}
}
boolean mustbe21 = false;
String[] befores2 = (String[]) installBefores.get (l2);
if (befores2 != null) {
for (int i = 0; i < befores2.length; i++) {
if (befores2[i].equals (rep1)) {
mustbe21 = true;
break;
}
if (befores2[i].equals (l1)) warn (l2, l1, rep1);
}
}
if (! mustbe21) {
String[] afters1 = (String[]) installAfters.get (l1);
if (afters1 != null) {
for (int i = 0; i < afters1.length; i++) {
if (afters1[i].equals (rep2)) {
mustbe21 = true;
break;
}
if (afters1[i].equals (l2)) warn (l1, l2, rep2);
}
}
}
// Compute resulting order.
if (mustbe12) {
if (mustbe21) {
if (Boolean.getBoolean ("netbeans.debug.exceptions")) // NOI18N
// PLEASE DO NOT COMMENT OUT:
System.err.println ("Warning: mutually contradictory loader ordering will be ignored; " +
l1 + " and " + l2); // NOI18N
return 0;
} else {
return -1;
}
} else {
if (mustbe21) {
return 1;
} else {
return 0;
}
}
}
private void warn (String yourLoader, String otherLoader, String otherRepn) {
// PLEASE DO NOT COMMENT OUT:
System.err.println ("Warning: a possible error in the manifest containing " + yourLoader + " was found."); // NOI18N
System.err.println ("The loader specified an Install-{After,Before} on " + otherLoader + ", but this is a DataLoader class."); // NOI18N
System.err.println ("Probably you wanted " + otherRepn + " which is the loader's representation class."); // NOI18N
}
};
try {
loaders = Utilities.partialSort (loaders, c, true);
} catch (Utilities.UnorderableException uue) {
if (Boolean.getBoolean ("netbeans.debug.exceptions")) // NOI18N
uue.printStackTrace ();
// leave order as it was
}
update ();
}
/** Notification to finish installation of nodes during startup.
* @deprecated no longer does anything, please remove calls to it
*/
static void installationFinished () {
installationFinished = true;
myChildren.update ();
}
/** Stores all the objects into stream.
* @param oos object output stream to write to
*/
private static synchronized void writePool (ObjectOutputStream oos)
throws IOException {
//System.err.println("writePool");
oos.writeObject (installBefores);
oos.writeObject (installAfters);
Iterator it = loaders.iterator ();
while (it.hasNext ()) {
DataLoader l = (DataLoader)it.next ();
NbMarshalledObject obj;
try {
obj = new NbMarshalledObject (l);
} catch (IOException ex) {
TopManager.getDefault ().notifyException (ex);
obj = null;
}
if (obj != null) {
//System.err.println("writing " + l.getDisplayName ());
oos.writeObject (obj);
}
}
//System.err.println("writing null");
oos.writeObject (null);
// Write out system loaders now:
Enumeration e = loaderPool.allLoaders ();
while (e.hasMoreElements ()) {
DataLoader l = (DataLoader) e.nextElement ();
if (loaders.contains (l)) continue;
NbMarshalledObject obj;
try {
obj = new NbMarshalledObject (l);
} catch (IOException ex) {
TopManager.getDefault ().notifyException (ex);
obj = null;
}
if (obj != null) {
//System.err.println("writing " + l.getDisplayName ());
oos.writeObject (obj);
}
}
//System.err.println("writing null");
oos.writeObject (null);
//System.err.println("done writing");
}
/** Reads loader from the input stream.
* @param ois object input stream to read from
*/
private static synchronized void readPool (ObjectInputStream ois)
throws IOException, ClassNotFoundException {
installBefores = (Map) ois.readObject ();
installAfters = (Map) ois.readObject ();
HashSet classes = new HashSet ();
LinkedList l = new LinkedList ();
for (;;) {
NbMarshalledObject obj = (NbMarshalledObject)ois.readObject ();
if (obj == null) {
//System.err.println("reading null");
break;
}
try {
DataLoader loader = (DataLoader)obj.get ();
//System.err.println("reading " + loader.getDisplayName ());
l.add (loader);
classes.add (loader.getClass ());
} catch (IOException ex) {
if (System.getProperty ("netbeans.debug.exceptions") != null) {
ex.printStackTrace();
}
} catch (ClassNotFoundException ex) {
if (System.getProperty ("netbeans.debug.exceptions") != null) {
ex.printStackTrace();
}
}
}
// Read system loaders. But not into any particular order.
for (;;) {
NbMarshalledObject obj = (NbMarshalledObject) ois.readObject ();
if (obj == null) {
//System.err.println("reading null");
break;
}
try {
// Just reads its shared state, nothing more.
DataLoader loader = (DataLoader) obj.get ();
//System.err.println("reading " + loader.getDisplayName ());
} catch (IOException ex) {
if (System.getProperty ("netbeans.debug.exceptions") != null) {
ex.printStackTrace();
}
} catch (ClassNotFoundException ex) {
if (System.getProperty ("netbeans.debug.exceptions") != null) {
ex.printStackTrace();
}
}
}
//System.err.println("done reading");
// Explanation: modules are permitted to restoreDefault () before
// the loader pool is de-externalized. This means that all loader manifest
// sections will add a default-instance entry to the pool at startup
// time. Later, when the pool is restored, this may reorder existing ones,
// as well as change properties. But if any loader is missing (typically
// due to failed deserialization), it will nonetheless be added to the end
// now (and the pool resorted just in case).
Iterator it = loaders.iterator ();
boolean readded = false;
while (it.hasNext ()) {
DataLoader loader = (DataLoader)it.next ();
if (!classes.contains (loader.getClass ())) {
l.add (loader);
readded = true;
}
}
loaders = l;
if (readded)
resort ();
else
update ();
}
/** Notification that the state of pool has changed
*/
private static synchronized void update () {
// clear the cache of loaders
loadersArray = null;
if (loaderPool != null && installationFinished) {
loaderPool.superFireChangeEvent();
myChildren.update ();
}
}
/** Removes the loader. It is only removed from the list but
* if an DataObject instance created exists it will be still
* valid.
* <P>
* So the only difference is that when a DataObject is searched
* for a FileObject this loader will not be taken into account.
* <P>The loader pool may be resorted.
* @param dl data loader to remove
* @return true if the loader was registered and false if not
*/
public static synchronized boolean remove (DataLoader dl) {
if (loaders.remove (dl)) {
installBefores.remove (dl.getClass ().getName ());
installAfters.remove (dl.getClass ().getName ());
resort ();
return true;
}
return false;
}
/** Returns the only instance of the loader pool node in our system.
* There's no other way to get an instance of this class,
* loader pool node is singleton.
* @return loader pool node instance
*/
public static synchronized LoaderPoolNode getLoaderPoolNode () {
if (loaderPoolNode == null)
loaderPoolNode = new LoaderPoolNode();
return loaderPoolNode;
}
/** Returns the only instance of the loader pool in our system.
* There's no other way to get an instance of this class,
* loader pool is singleton too.
* @return loader pool instance
*/
public static NbLoaderPool getNbLoaderPool () {
if (loaderPool == null)
loaderPool = new LoaderPoolNode.NbLoaderPool();
return loaderPool;
}
/***** Inner classes **************/
/** Node representing one loader in Loader Pool */
private static class LoaderPoolItemNode extends BeanNode {
/** true if a system loader */
boolean isSystem;
/**
* Constructs LoaderPoolItemNode for specified DataLoader.
*
* @param theBean bean for which we can construct BeanNode
* @param parent The parent of this node.
*/
public LoaderPoolItemNode(DataLoader loader) throws IntrospectionException {
super(loader);
isSystem = ! loaders.contains (loader);
if (isSystem) {
setSynchronizeName (false);
setDisplayName (MessageFormat.format (NbBundle.getBundle (LoaderPoolNode.class).getString ("LBL_system_data_loader"),
new Object[] { getDisplayName () }));
}
}
/** Getter for set of actions that should be present in the
* popup menu of this node.
*
* @return array of system actions that should be in popup menu
*/
public SystemAction[] createActions () {
if (isSystem)
return new SystemAction[] {
SystemAction.get(ToolsAction.class),
SystemAction.get(PropertiesAction.class),
};
else
return new SystemAction[] {
SystemAction.get(MoveUpAction.class),
SystemAction.get(MoveDownAction.class),
null,
SystemAction.get(ToolsAction.class),
SystemAction.get(PropertiesAction.class),
};
}
/** @return true
*/
public SystemAction getDefaultAction () {
return SystemAction.get (PropertiesAction.class);
}
/** Can be deleted.
*/
public boolean canDestroy () {
return false;
}
/*
// Removed: deleted loaders would reappear after a reload of the pool anyway.
public void destroy () throws IOException {
remove ((DataLoader) getBean ());
}
*/
/** Cannot be copied
*/
public boolean canCopy () {
return false;
}
/** Cannot be cut
*/
public boolean canCut () {
return false;
}
} // end of LoaderPoolItemNode
/** Implementation of children for LoaderPool node in explorer.
* Extends Index.MapChildren implementation to map nodes to loaders and to support
* children reordering.
*/
private static final class LoaderChildren extends Children.Keys {
/** Update the the nodes */
public void update () {
List _loaders = new LinkedList ();
// Should not need an explicit synch, NBLP.loaders() does this:
Enumeration e = loaderPool.allLoaders ();
while (e.hasMoreElements ()) _loaders.add (e.nextElement ());
setKeys (_loaders);
Iterator it = _loaders.iterator ();
while (it.hasNext ()) {
DataLoader l = (DataLoader)it.next ();
// so the pool is there only once
l.removePropertyChangeListener (loaderPool);
l.addPropertyChangeListener (loaderPool);
}
}
/** Creates new node for the loader.
*/
protected Node[] createNodes (Object loader) {
Node n;
try {
return new Node[] { new LoaderPoolItemNode ((DataLoader)loader) };
} catch (IntrospectionException e) {
if (Boolean.getBoolean ("netbeans.debug.exceptions")) // NOI18N
e.printStackTrace ();
return new Node[] { };
}
}
} // end of LoaderPoolChildren
/** Concrete implementation of and abstract DataLoaderPool
* (former CoronaLoaderPool).
* Being a singleton, this class is private and the only system instance
* can be obtained via LoaderPoolNode.getNbLoaderPool() call.
* Delegates its work to the outer class LoaderPoolNode.
*/
public static final class NbLoaderPool extends DataLoaderPool
implements PropertyChangeListener, Runnable {
private static final long serialVersionUID =-8488524097175567566L;
private RequestProcessor.Task fireTask = RequestProcessor.createRequest (this);
/** Enumerates all loaders. Loaders are taken from children
* structure of LoaderPoolNode. */
protected Enumeration loaders () {
//
// prevents from extensive copying
//
Object[] arr = loadersArray;
if (arr == null) {
synchronized (LoaderPoolNode.class) {
arr = loadersArray = loaders.toArray ();
}
}
return new ArrayEnumeration (arr);
}
/** Listener to property changes.
*/
public void propertyChange (PropertyChangeEvent ev) {
if (installationFinished) {
superFireChangeEvent ();
};
}
/** Fires change event to all listeners
* (Delegates all work to its superclass)
* Accessor for inner classes only.
* @param che change event
*/
void superFireChangeEvent () {
fireTask.schedule (1000);
}
/** Called from the request task */
public void run () {
super.fireChangeEvent(new ChangeEvent (this));
//System.out.println ("Loaders Change event fired...."); // NOI18N
}
/** Write the object.
*/
private void writeObject (ObjectOutputStream oos) throws IOException {
LoaderPoolNode.writePool (oos);
}
/** Reads the object.
*/
private void readObject (ObjectInputStream ois)
throws IOException, ClassNotFoundException {
LoaderPoolNode.readPool (ois);
}
/** Replaces the pool with default instance.
*/
private Object readResolve () {
return getNbLoaderPool ();
}
} // end of NbLoaderPool
/** Index support for reordering of file system pool.
*/
private final class Index extends org.openide.nodes.Index.Support {
/** Get the nodes; should be overridden if needed.
* @return the nodes
* @throws NotImplementedException always
*/
public Node[] getNodes () {
Enumeration e = getChildren ().nodes ();
List l = new ArrayList ();
while (e.hasMoreElements ()) {
LoaderPoolItemNode node = (LoaderPoolItemNode) e.nextElement ();
if (! node.isSystem) l.add (node);
}
return (Node[]) l.toArray (new Node[l.size ()]);
}
/** Get the node count. Subclasses must provide this.
* @return the count
*/
public int getNodesCount () {
return getNodes ().length;
}
/** Reorder by permutation. Subclasses must provide this.
* @param perm the permutation
*/
public void reorder (int[] perm) {
synchronized (LoaderPoolNode.class) {
Object[] arr = loaders.toArray ();
if (arr.length == perm.length) {
Object[] target = new Object[arr.length];
for (int i = 0; i < arr.length; i++) {
if (target[perm[i]] != null) {
throw new IllegalArgumentException ();
}
target[perm[i]] = arr[i];
}
loaders = new ArrayList (Arrays.asList (target));
update ();
} else {
throw new IllegalArgumentException ();
}
}
}
} // End of Index
}
/*
* Log
* 36 Gandalf 1.35 1/16/00 Jaroslav Tulach TemplatesExplorer
* removed, startup faster
* 35 Gandalf 1.34 1/13/00 Jesse Glick System loaders are
* annotated as such.
* 34 Gandalf 1.33 1/13/00 Jaroslav Tulach I18N
* 33 Gandalf 1.32 1/13/00 Jesse Glick All loaders are displayed
* and persisted, incl. system ones, though these cannot be reordered.
* 32 Gandalf 1.31 12/2/99 Jesse Glick Loaders cannot be removed
* from pool, either intentionally or accidentally (e.g. after failed
* deserialize).
* 31 Gandalf 1.30 11/26/99 Patrik Knakal
* 30 Gandalf 1.29 11/26/99 Jesse Glick Fixed a
* ConcurrentModificationException, and also added a proper svuid.
* 29 Gandalf 1.28 11/25/99 Jesse Glick Rewrite of
* LoaderPoolNode, specifically the management of loader ordering. Now
* permits multiple -before and -after dependencies, and should be more
* robust. Also made LoaderPoolItemNode's properly deletable and fixed a
* timing-related NullPointerException when uninstalling modules.
* 28 Gandalf 1.27 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 27 Gandalf 1.26 10/8/99 Jaroslav Tulach Prints exceptions only to
* console.
* 26 Gandalf 1.25 9/30/99 Jaroslav Tulach DataLoader is now
* serializable.
* 25 Gandalf 1.24 9/28/99 Jaroslav Tulach Changes in loader pool
* are reflected in repository.
* 24 Gandalf 1.23 8/30/99 Jaroslav Tulach Notification of change of
* loaders in different thread.
* 23 Gandalf 1.22 7/8/99 Jesse Glick Context help.
* 22 Gandalf 1.21 6/9/99 Ian Formanek ToolsAction
* 21 Gandalf 1.20 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 20 Gandalf 1.19 5/12/99 Jaroslav Tulach NullPointer fix.
* 19 Gandalf 1.18 5/11/99 Jaroslav Tulach ToolbarPool changed to
* look better in Open API
* 18 Gandalf 1.17 5/9/99 Ian Formanek Fixed bug 1655 - Renaming
* of top level nodes is not persistent (removed the possibility to
* rename).
* 17 Gandalf 1.16 5/4/99 Jaroslav Tulach Relative URL for modules.
* 16 Gandalf 1.15 4/15/99 Martin Ryzl add fixed
* 15 Gandalf 1.14 4/7/99 Ian Formanek Rename
* Section->ManifestSection
* 14 Gandalf 1.13 3/30/99 Jaroslav Tulach Form loader before Java
* loaderem.
* 13 Gandalf 1.12 3/26/99 Ian Formanek Fixed use of obsoleted
* NbBundle.getBundle (this)
* 12 Gandalf 1.11 3/25/99 Jaroslav Tulach Loader pool order fixed.
* 11 Gandalf 1.10 3/24/99 Ian Formanek
* 10 Gandalf 1.9 3/24/99 Ian Formanek
* 9 Gandalf 1.8 3/18/99 Ian Formanek
* 8 Gandalf 1.7 3/18/99 Jaroslav Tulach
* 7 Gandalf 1.6 2/16/99 David Simonek
* 6 Gandalf 1.5 1/20/99 Jaroslav Tulach
* 5 Gandalf 1.4 1/7/99 David Simonek
* 4 Gandalf 1.3 1/7/99 Ian Formanek fixed resource names
* 3 Gandalf 1.2 1/6/99 Ian Formanek Reflecting change in
* datasystem package
* 2 Gandalf 1.1 1/6/99 Ian Formanek Fixed outerclass
* specifiers uncompilable under JDK 1.2
* 1 Gandalf 1.0 1/5/99 Ian Formanek
* $
*/